
#include <ctime>
#include <thread>
#include "Timer.h"

using namespace UTILS;

bool TimerId::valid() const { return !timer_.expired(); }

void TimerId::destory() { timer_.reset(); }

Timer::Timer() : workGuard_(make_work_guard(io_)) {
  thd_ = std::thread([this] {
    io_.run();
    // TODO add log
  });
  // TODO add log
}

Timer::~Timer() { clear(); }

Timer &Timer::getInstance() {
  static Timer timer;
  return timer;
}

TimerId Timer::runEvery(int64_t interval, TimerHandler_t handler) {
  auto timerInfo = std::make_shared<TimerInfo>(io_);
  std::weak_ptr<TimerInfo> wptr = timerInfo;

  timerInfo->handler_ = [wptr, handler,
                         interval](const asio::error_code &ec) {
    if (!ec) [[likely]] {
      handler();
      auto pTimerInfo = wptr.lock();
      if (pTimerInfo) {
        pTimerInfo->timer_.expires_at(pTimerInfo->timer_.expiry() +
                                      std::chrono::milliseconds(interval));
        pTimerInfo->timer_.async_wait(pTimerInfo->handler_);
      }
    }
  };
  timerInfo->timer_.expires_after(std::chrono::milliseconds(interval));
  timerInfo->timer_.async_wait(timerInfo->handler_);
  std::unique_lock<std::mutex> lk(mtx_);
  timers_.insert(timerInfo);

  return TimerId(timerInfo);
}

TimerId Timer::runAfter(int64_t delay, TimerHandler_t handler) {
  auto timerInfo = std::make_shared<TimerInfo>(io_);
  std::weak_ptr<TimerInfo> wptr = timerInfo;

  timerInfo->handler_ = [this, wptr,
                         handler](const asio::error_code &ec) {
    auto pTimerInfo = wptr.lock();
    if (pTimerInfo) {
      TimerId id(pTimerInfo);
      cancel(id);
    }
    if (!ec) {
      handler();
    }
  };

  timerInfo->timer_.expires_after(std::chrono::milliseconds(delay));
  timerInfo->timer_.async_wait(timerInfo->handler_);
  std::unique_lock<std::mutex> lk(mtx_);
  timers_.insert(timerInfo);

  return TimerId(timerInfo);
}

void Timer::cancel(TimerId &timer) {
  auto p = timer.timer_.lock();
  if (p) {
    p->timer_.cancel();
    std::unique_lock<std::mutex> lk(mtx_);
    timers_.erase(p);
  }
  timer.destory();
}

void Timer::clear() {
  if (!io_.stopped()) {
    io_.stop();
  }
  if (thd_.joinable()) {
    thd_.join();
  }
}

bool Timer::isTimerExpired(TimerId &timer) {
  bool expired = true;
  auto p = timer.timer_.lock();
  if (p) {
    std::unique_lock<std::mutex> lk(mtx_);
    expired = timers_.find(p) == timers_.end();
  }
  return expired;
}

long long int Timer::get_time_ms() {
  struct timeval tv {};
  struct timezone tz {};

  long long int sample_tms = 0;

  gettimeofday(&tv, &tz);
  sample_tms = (long long int)tv.tv_sec * 1000 + tv.tv_usec / 1000;

  return sample_tms;
}

int64_t Timer::currentTime() {
  struct timeval tv {};
  struct timezone tz {};

  gettimeofday(&tv, &tz);
  return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
